Skip to content

Enable report generation#150

Merged
Vaibhav978 merged 14 commits intomainfrom
add-report-generation
Apr 12, 2026
Merged

Enable report generation#150
Vaibhav978 merged 14 commits intomainfrom
add-report-generation

Conversation

@nourshoreibah
Copy link
Copy Markdown
Collaborator

@nourshoreibah nourshoreibah commented Feb 22, 2026

ℹ️ Issue

Closes #141

📝 Description

  • Add POST /reports endpoint that generates a PDF report for a project , uploads to S3, and saves the record
  • Add forgot-password and reset-password flows to auth service
  • Resolve some auth bugs
  • Improve auth verify-email error handling
  • Make make up run in foreground (blocking) instead of detached
  • Add AGENTS.md documenting the lambda CLI usage
  • Add lots of tests for reports

Write a short summary of what you added. Why is it important? Any member of C4C should be able to read this and understand your contribution -- not just your team members.

Briefly list the changes made to the code:

  1. Added support for this.
  2. And removed redunant use of that.
  3. Also this was included for reasons.

✔️ Verification

  1. make up from apps/backend/
  2. Sign up via POST /auth/signup, verify email via POST /auth/verify-email
  3. Log in via POST /auth/login to get a JWT
  4. Ensure your user is admin (UPDATE branch.users SET is_admin = TRUE WHERE email = '...') or has a project membership
  5. Open http://localhost:3005/reports/swagger, authorize with Bearer token
  6. POST /reports with {"project_id": 1} -> 201 with object_url
  7. Open object_url in browser

Generated this: https://c4c-branch-generated-reports20251030194253425700000001.s3.amazonaws.com/reports/1/2026-02-22T05-20-11-521Z.pdf

🏕️ (Optional) Future Work / Notes

Did you notice anything ugly during the course of this ticket? Any bugs, design challenges, or unexpected behavior? Write it down so we can clean it up in a future ticket!

@nourshoreibah nourshoreibah linked an issue Feb 22, 2026 that may be closed by this pull request
@nourshoreibah nourshoreibah marked this pull request as ready for review February 22, 2026 05:31
github-actions bot added a commit that referenced this pull request Feb 22, 2026
nourshoreibah and others added 4 commits February 22, 2026 00:38
  - Auto-formatted .tf files with terraform fmt
  - Updated README.md with terraform-docs

  Co-authored-by: nourshoreibah <nourshoreibah@users.noreply.github.com>
}),
);

return `https://${bucketName}.s3.amazonaws.com/${key}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be wrong about this but you could use the s3 client config for this. does this default to us-east-2


const s3 = new S3Client({ region: process.env.AWS_REGION ?? 'us-east-2' });

function getBucketName(): string {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could add validation for env vars to the handler instead of blank string

projectId: number,
isAdmin: boolean,
): Promise<boolean> {
if (isAdmin) return true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you need to check if the project exists first?

Resolve merge conflicts to retain both POST /reports (PDF generation)
and GET /reports (paginated list) endpoints, merging dependencies,
tests, and OpenAPI specs from both branches.

Made-with: Cursor
@nourshoreibah nourshoreibah enabled auto-merge April 8, 2026 02:44
github-actions bot and others added 2 commits April 8, 2026 02:44
Pass URLResolver to PdfPrinter constructor (required since 0.3.7) and
reorder handler to check project existence before access so non-existent
projects return 404 instead of 403.

Made-with: Cursor
@nourshoreibah nourshoreibah force-pushed the add-report-generation branch from c5f10a1 to 0869c66 Compare April 8, 2026 02:52
@nourshoreibah nourshoreibah added this pull request to the merge queue Apr 8, 2026
@nourshoreibah nourshoreibah removed this pull request from the merge queue due to a manual request Apr 8, 2026
@nourshoreibah nourshoreibah added this pull request to the merge queue Apr 8, 2026
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Apr 8, 2026
@Vaibhav978 Vaibhav978 self-requested a review as a code owner April 12, 2026 23:13
@github-actions
Copy link
Copy Markdown
Contributor

Terraform Plan 📖 infrastructure/aws

Terraform Initialization ⚙️success

Terraform Validation 🤖success

Terraform Plan 📖success

Show Plan
data.archive_file.lambda_placeholder: Reading...
data.archive_file.lambda_placeholder: Read complete after 0s [id=96878a51e358033297a32b882fd5223cc95fb8a7]
data.aws_caller_identity.current: Reading...
aws_api_gateway_rest_api.branch_api: Refreshing state... [id=2apxzxb0r8]
aws_iam_role.lambda_role: Refreshing state... [id=branch-lambda-role]
aws_cognito_user_pool.branch_user_pool: Refreshing state... [id=us-east-2_CxTueqe6g]
aws_s3_bucket.reports_bucket: Refreshing state... [id=c4c-branch-generated-reports20251030194253425700000001]
data.aws_caller_identity.current: Read complete after 0s [id=489881683177]
aws_s3_bucket.lambda_deployments: Refreshing state... [id=branch-lambda-deployments-489881683177]
aws_api_gateway_resource.lambda_resources["projects"]: Refreshing state... [id=chhy2i]
aws_api_gateway_resource.lambda_resources["auth"]: Refreshing state... [id=u8unad]
aws_api_gateway_resource.lambda_resources["expenditures"]: Refreshing state... [id=6sdj3w]
aws_api_gateway_resource.lambda_resources["users"]: Refreshing state... [id=0dkbds]
aws_api_gateway_resource.lambda_resources["reports"]: Refreshing state... [id=wsnfk2]
aws_api_gateway_resource.lambda_resources["donors"]: Refreshing state... [id=hybur2]
aws_iam_role_policy_attachment.lambda_basic: Refreshing state... [id=branch-lambda-role/arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole]
aws_api_gateway_method.lambda_methods["projects-POST"]: Refreshing state... [id=agm-2apxzxb0r8-chhy2i-POST]
aws_api_gateway_method.lambda_methods["users-POST"]: Refreshing state... [id=agm-2apxzxb0r8-0dkbds-POST]
aws_api_gateway_method.lambda_methods["reports-GET"]: Refreshing state... [id=agm-2apxzxb0r8-wsnfk2-GET]
aws_api_gateway_method.lambda_methods["expenditures-GET"]: Refreshing state... [id=agm-2apxzxb0r8-6sdj3w-GET]
aws_api_gateway_method.lambda_methods["auth-POST"]: Refreshing state... [id=agm-2apxzxb0r8-u8unad-POST]
aws_api_gateway_method.lambda_methods["expenditures-POST"]: Refreshing state... [id=agm-2apxzxb0r8-6sdj3w-POST]
aws_api_gateway_method.lambda_methods["donors-GET"]: Refreshing state... [id=agm-2apxzxb0r8-hybur2-GET]
aws_api_gateway_method.lambda_methods["users-GET"]: Refreshing state... [id=agm-2apxzxb0r8-0dkbds-GET]
aws_api_gateway_method.lambda_methods["projects-GET"]: Refreshing state... [id=agm-2apxzxb0r8-chhy2i-GET]
aws_api_gateway_method.lambda_methods["users-DELETE"]: Refreshing state... [id=agm-2apxzxb0r8-0dkbds-DELETE]
aws_api_gateway_method.lambda_methods["auth-GET"]: Refreshing state... [id=agm-2apxzxb0r8-u8unad-GET]
aws_api_gateway_method.lambda_methods["users-PATCH"]: Refreshing state... [id=agm-2apxzxb0r8-0dkbds-PATCH]
aws_cognito_user_pool_client.branch_client: Refreshing state... [id=570i6ocj0882qu0ditm4vrr60f]
aws_s3_bucket_versioning.lambda_deployments: Refreshing state... [id=branch-lambda-deployments-489881683177]
aws_s3_bucket_server_side_encryption_configuration.lambda_deployments: Refreshing state... [id=branch-lambda-deployments-489881683177]
aws_s3_object.lambda_placeholder["users"]: Refreshing state... [id=branch-lambda-deployments-489881683177/users/initial.zip]
aws_s3_object.lambda_placeholder["reports"]: Refreshing state... [id=branch-lambda-deployments-489881683177/reports/initial.zip]
aws_s3_object.lambda_placeholder["donors"]: Refreshing state... [id=branch-lambda-deployments-489881683177/donors/initial.zip]
aws_s3_object.lambda_placeholder["projects"]: Refreshing state... [id=branch-lambda-deployments-489881683177/projects/initial.zip]
aws_s3_object.lambda_placeholder["auth"]: Refreshing state... [id=branch-lambda-deployments-489881683177/auth/initial.zip]
aws_s3_object.lambda_placeholder["expenditures"]: Refreshing state... [id=branch-lambda-deployments-489881683177/expenditures/initial.zip]
aws_s3_bucket_public_access_block.reports_bucket_public_access: Refreshing state... [id=c4c-branch-generated-reports20251030194253425700000001]
aws_s3_bucket_policy.reports_bucket_policy: Refreshing state... [id=c4c-branch-generated-reports20251030194253425700000001]
data.infisical_secrets.github_folder: Reading...
data.infisical_secrets.rds_folder: Reading...
data.infisical_secrets.rds_folder: Read complete after 0s
aws_db_instance.branch_rds: Refreshing state... [id=db-AMMYFTORW6XJGRELV7WQZCNHQI]
data.infisical_secrets.github_folder: Read complete after 0s
aws_amplify_app.frontend: Refreshing state... [id=dbcy90q4o31f7]
aws_lambda_function.functions["auth"]: Refreshing state... [id=branch-auth]
aws_lambda_function.functions["expenditures"]: Refreshing state... [id=branch-expenditures]
aws_lambda_function.functions["projects"]: Refreshing state... [id=branch-projects]
aws_lambda_function.functions["reports"]: Refreshing state... [id=branch-reports]
aws_lambda_function.functions["users"]: Refreshing state... [id=branch-users]
aws_lambda_function.functions["donors"]: Refreshing state... [id=branch-donors]
aws_amplify_branch.main: Refreshing state... [id=dbcy90q4o31f7/main]
aws_api_gateway_integration.lambda_integrations["expenditures-GET"]: Refreshing state... [id=agi-2apxzxb0r8-6sdj3w-GET]
aws_lambda_permission.api_gateway_permissions["expenditures"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_api_gateway_integration.lambda_integrations["users-POST"]: Refreshing state... [id=agi-2apxzxb0r8-0dkbds-POST]
aws_lambda_permission.api_gateway_permissions["auth"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_lambda_permission.api_gateway_permissions["projects"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_api_gateway_integration.lambda_integrations["users-DELETE"]: Refreshing state... [id=agi-2apxzxb0r8-0dkbds-DELETE]
aws_lambda_permission.api_gateway_permissions["reports"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_api_gateway_integration.lambda_integrations["users-GET"]: Refreshing state... [id=agi-2apxzxb0r8-0dkbds-GET]
aws_lambda_permission.api_gateway_permissions["users"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_lambda_permission.api_gateway_permissions["donors"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_api_gateway_integration.lambda_integrations["auth-GET"]: Refreshing state... [id=agi-2apxzxb0r8-u8unad-GET]
aws_api_gateway_integration.lambda_integrations["users-PATCH"]: Refreshing state... [id=agi-2apxzxb0r8-0dkbds-PATCH]
aws_api_gateway_integration.lambda_integrations["projects-POST"]: Refreshing state... [id=agi-2apxzxb0r8-chhy2i-POST]
aws_api_gateway_integration.lambda_integrations["expenditures-POST"]: Refreshing state... [id=agi-2apxzxb0r8-6sdj3w-POST]
aws_api_gateway_integration.lambda_integrations["auth-POST"]: Refreshing state... [id=agi-2apxzxb0r8-u8unad-POST]
aws_api_gateway_integration.lambda_integrations["projects-GET"]: Refreshing state... [id=agi-2apxzxb0r8-chhy2i-GET]
aws_api_gateway_integration.lambda_integrations["donors-GET"]: Refreshing state... [id=agi-2apxzxb0r8-hybur2-GET]
aws_api_gateway_integration.lambda_integrations["reports-GET"]: Refreshing state... [id=agi-2apxzxb0r8-wsnfk2-GET]
aws_api_gateway_deployment.branch_deployment: Refreshing state... [id=klh9jv]
aws_api_gateway_stage.branch_stage: Refreshing state... [id=ags-2apxzxb0r8-prod]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_amplify_app.frontend will be updated in-place
  ~ resource "aws_amplify_app" "frontend" {
      ~ environment_variables         = {
          + "NEXT_PUBLIC_API_BASE_URL"  = null
            # (1 unchanged element hidden)
        }
        id                            = "dbcy90q4o31f7"
        name                          = "branch-frontend"
        tags                          = {}
        # (19 unchanged attributes hidden)

        # (2 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

Pushed by: @Vaibhav978, Action: pull_request

Copy link
Copy Markdown
Contributor

@Vaibhav978 Vaibhav978 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

}

const { user } = authContext;
const body = event.body ? JSON.parse(event.body) as Record<string, unknown> : {};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: we should add in a check in case the JSON is malformed.

@Vaibhav978 Vaibhav978 added this pull request to the merge queue Apr 12, 2026
Merged via the queue into main with commit c07be5c Apr 12, 2026
14 checks passed
@Vaibhav978 Vaibhav978 deleted the add-report-generation branch April 12, 2026 23:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MVP Report Generation

4 participants